/*
 * AnsiTermTokenizer.java
 * 
 * Copyright 2007 Kamen Petroff.
 * All Rights Reserved.
 * 
 * This software is the proprietary information of Kamen Petroff.
 * Use is subject to license terms.
 * 
 * Created on Mar 3, 2007 at 10:32:34 PM by Kamen Petroff
 */
package com.google.code.shell4eclipse.console;

/**
 * 
 *
 * @author Kamen Petroff 
 * @since Mar 3, 2007
 */
public class AnsiTermTokenizer {
	private ScannerState state = ScannerState.NORMAL;
	
	private byte[] buffer = new byte[1024];
	private int position = 0;
	
	public static enum ScannerState {
		NORMAL,
		ESC,
		ESCAPED,
		ERROR
	};
	
	public static class Token {
		private byte[] esc;
		private String token;
		private ScannerState state;
		
		public Token(String token) {
			this(ScannerState.NORMAL);
			this.token = token;
		}
		public Token(byte[] esc) {
			this(ScannerState.ESCAPED);
			this.esc = esc;
		}
		public Token(ScannerState state) {
			this.state = state;
		}
		
		public byte[] getEscapedSequence() {
			return esc;
		}
		public String getToken() {
			return this.token;
		}
		public ScannerState getState() {
			return this.state;
		}
	}
	
	private void append(byte[] buf, int len) {
		if(len < 1) {
			return;
		}
		if(len < (buffer.length - position)) {
			System.arraycopy(buf, 0, buffer, position, len);
		} else { // grow
			byte[] tmp = new byte[buffer.length+len];
			System.arraycopy(buffer, 0, tmp, 0, position);
			buffer = tmp;
			System.arraycopy(buf, 0, buffer, position, len);
		}
		position += len;
	}
	
	public Token next(byte[] buf, int len) {
		append(buf, len);
		Token result = null;
		
		switch(state) {
		case NORMAL:
			int pos = indexOf((byte)033);
			if(pos == -1) {
				Token token = position>0?new Token(new String(buffer, 0, position)):null;
				position = 0;
				result = token;
			} else {
				Token token = pos>0?new Token(new String(buffer, 0, pos)):null;
				pos ++;
				System.arraycopy(buffer, pos, buffer, 0, position - pos);
				position -= pos;
				state = ScannerState.ESC;
				result = token;
			}
			break;
		case ESCAPED:
			pos = indexOf((byte)109);
			if(pos != -1) {
				byte[] esc = new byte[pos];
				System.arraycopy(buffer, 0, esc, 0, pos);
				if(pos < position) {
					pos ++;
					System.arraycopy(buffer, pos, buffer, 0, position - pos);
					position -= pos;
				} else {
					position = 0;
				}
				state = ScannerState.NORMAL;
				return new Token(esc);
			}
			return null;
		case ESC:
			if(buffer[0] == (byte)91) {
				System.arraycopy(buffer, 1, buffer, 0, position - 1);
				position -= 1;
				state = ScannerState.ESCAPED;
				result = null;
			} else if(buffer[0] == (byte)93) {
				pos = indexOf((byte)033);
				if(pos != -1) {
					pos ++;
					System.arraycopy(buffer, pos, buffer, 0, position - pos);
					position -= pos;
				}
			} else {
				state = ScannerState.ERROR;
				return new Token(state);
			}
			break;
		case ERROR:
			System.err.println("Recovering ERROR ScannerState.");
			state = ScannerState.NORMAL; 
		}
		
		if(result == null && position > 0) {
			return next(buf, 0);
		} 
		return result;
	}
	
	private int indexOf(byte t) {
		for(int i = 0; i < position; i++) {
			if(buffer[i] == t) {
				return i;
			} 
		}
		return -1;
	}

}
